Skip to content

fix(bin): append the launch brief to raw launch commands - #7829

Open
trillium wants to merge 10 commits into
kunchenguid:mainfrom
trillium:fix/spawn-raw-launch-brief
Open

trillium wants to merge 10 commits into
kunchenguid:mainfrom
trillium:fix/spawn-raw-launch-brief

Conversation

@trillium

@trillium trillium commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Intent

main firstmate is down, please handle comments on kun chen firstmate PRs. For this PR (append the launch brief to raw launch commands): Greptile flagged a P1 at bin/fm-spawn.sh around line 2911. Compound raw launch commands must be handled explicitly or rejected with guidance to put BRIEF in the agent command. Failing shapes: 'custom-agent --flag; cleanup' (the brief becomes an argument to cleanup) and 'custom-agent --flag;' (the shell tries to run the encoded brief). Kun's firstmate also asked for one real raw launch with a real harness CLI, posting what the agent received, or re-raising with that evidence; the author already said kimi and rovo are not installed, so state that a generic harness only can be shown in the PR. The PR's intent step must complete in the pipeline attestation, and the lint step must report no open warning.

What Changed

  • bin/fm-spawn.sh now appends the launch brief to a raw launch command only when it is a plain single command for a known positional-prompt harness. The first word must be claude, codex, pi, pi-signed, omp, grok, cursor, gemini, devin, commandcode, cortex or muse. The command must contain none of $(, backtick, |, ;, &, <, > or a newline, must not start with a VAR=value assignment, and a wrapper such as env, nice or sudo disqualifies it. Raw claude gets the record-backed __BRIEFDOORBELL__. Other qualifying harnesses get "$(__OPINPUT__ encode launch-brief < __BRIEF__)" as their last argument.
  • Commands that already name __BRIEF__/__BRIEFDOORBELL__ are left as written, and raw kimi/rovo still get their brief pointer after the readiness gate. Any other raw command launches unchanged with a one-line warning to put __BRIEF__ where the agent takes its prompt. That covers compound, substituted, wrapped and unknown-program commands, including custom-agent --flag; cleanup and custom-agent --flag;. The spawn usage comment describes these rules.
  • tests/fm-spawn-dispatch-profile.test.sh adds tests for:
    • raw rovo getting no positional brief
    • a plain codex --flag getting the brief appended
    • raw claude getting the doorbell
    • nine compound, substituted, wrapped and unknown-program commands launching unchanged with the warning
    • a compound command that places its brief at its own __BRIEF__ placeholder

Note: kimi and rovo are not installed on the author's machine, so the PR can only show a real raw launch through a generic harness.

Risk Assessment

✅ Low: The raw-launch brief append now follows the recorded conservative rule: it appends only for plain single commands of known positional-prompt harnesses, uses the doorbell for claude, and leaves kimi/rovo and commands with BRIEF alone; every other command launches unchanged with a warning. The round-1 fix (dropping opencode, polytoken and agy) is in place, and the behavior tests drive real spawn runs.

Testing

Ran the targeted spawn test file (all raw-launch tests pass). Then drove six real fm-spawn raw launches in a disposable lab on a private tmux socket. Two generic-harness compound shapes (Greptile's failing cases) launched unchanged with the warning, and neither the agent nor cleanup got a brief argument. Placing BRIEF in a compound command put the typed envelope at the placeholder. A real Claude Code worker launched raw got the doorbell, read the published brief record and began the brief. Wrapped and command-substituted launches of the real claude binary ran unchanged with the warning. Evidence is saved as CLI transcripts and pane captures; there is no UI surface. The lab was fully torn down, including one stray treehouse pool the first spawn created under ~/.treehouse before TREEHOUSE_ROOT was set on the lab tmux server. Overall result: all scenarios pass. kimi and rovo are not installed, so their exemption was covered only by the existing test.

  • Live validation: ✅ go - 5 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Compound raw launch 'custom-agent --flag; cleanup' launches unchanged with a BRIEF warning; cleanup gets no brief argument ✅ pass live raw-generic-live.txt (Scenario A): spawn printed the warning, the staged launch ends '; custom-agent --flag; cleanup', custom-agent argv was only --flag, and cleanup argc=0
Trailing-semicolon raw launch 'custom-agent --flag;' launches unchanged with the warning; no encoded brief reaches the shell ✅ pass live raw-generic-live.txt (Scenario B): warning printed, staged launch ends 'custom-agent --flag;', agent argv was only --flag
BRIEF placed inside a compound command delivers the typed launch-brief envelope at that position with no warning ✅ pass live raw-generic-live.txt (Scenario C): no warning; argv[2] begins with U+2063 'FIRSTMATE_OP: v1 launch-brief: # Current worker role contract'; staged line keeps '; cleanup' after the placeholder
Real raw launch with a real harness CLI: 'claude --model haiku --setting-sources project,local' gets the record-backed doorbell and the worker reads the brief ✅ pass live raw-claude-live.txt: staged launch ends with the ': Firstmate operational input waiting: read <operational-inbox>/...msg' doorbell; the record holds the U+2063 launch-brief envelope; the Claude Code 2…
Adversarial: a wrapped ('env -u CLAUDECODE claude --version') or $(...)-argument raw launch of a known harness is not given the brief and warns ✅ pass live raw-adversarial-live.txt: both printed the warning, the staged launches end exactly as given, both ran ('2.1.285 (Claude Code)'), and the only operational record in the lab came from the plain claude…
Raw kimi/rovo launches get no positional brief (post-readiness pointer path) ⏸️ untested no The kimi and rovo CLIs are not on PATH on this machine (the author confirmed they are not installed), and installing them is out of scope for this gate. Only the generic-harness and claude paths could…
Evidence: Live generic-harness raw launches (compound, trailing ';', __BRIEF__ placement)

Source: Live generic-harness raw launches (compound, trailing ';', BRIEF placement)

\### Scenario A: raw 'custom-agent --flag; cleanup'
fm-gate-refuse: gate agent lifecycle permitted only against lab home /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp
warning: raw launch command is not a plain single command for a known harness (it is compound, substituted, wrapped, or starts with an unknown program), so the launch brief was not appended; put __BRIEF__ where the agent takes its prompt, for example: my-agent --flag "$(__OPINPUT__ encode launch-brief < __BRIEF__)"
spawned rawlab-cmpd harness=custom-agent kind=ship mode=local-only yolo=on window=primary:fm-rawlab-cmpd worktree=~/.treehouse/notes-24f9ca/1/notes
spawn-exit=0
staged tail: || exit 1; custom-agent --flag; cleanup
custom-agent argv:
argc=1
argv[1]=--flag
cleanup argv:
cleanup argc=0

\### Scenario B: raw 'custom-agent --flag;'
fm-gate-refuse: gate agent lifecycle permitted only against lab home /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp
warning: raw launch command is not a plain single command for a known harness (it is compound, substituted, wrapped, or starts with an unknown program), so the launch brief was not appended; put __BRIEF__ where the agent takes its prompt, for example: my-agent --flag "$(__OPINPUT__ encode launch-brief < __BRIEF__)"
spawned rawlab-trail harness=custom-agent kind=ship mode=local-only yolo=on window=primary:fm-rawlab-trail worktree=/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/treehouse/.treehouse/notes-24f9ca/1/notes
spawn-exit=0
staged tail: ev/null || exit 1; custom-agent --flag;

\### Scenario C: __BRIEF__ placement in compound command
custom-agent --flag "$(__OPINPUT__ encode launch-brief < __BRIEF__)"; cleanup
fm-gate-refuse: gate agent lifecycle permitted only against lab home /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp
WARNING: watcher still down (same stale episode; last beat: never, grace 300s) - full banner already printed this episode.
spawned rawlab-place harness=custom-agent kind=ship mode=local-only yolo=on window=primary:fm-rawlab-place worktree=/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/treehouse/.treehouse/notes-24f9ca/2/notes
spawn-exit=0
staged tail: ent --flag "$('~/.no-mistakes/worktrees/cb10e94bf57f/01M65TR9BJBJP5K211NC45WDXY/bin/fm-operational-input.sh' encode launch-brief < '/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/data/rawlab-place/launch-brief.md')"; cleanup
custom-agent argv (first 500 bytes, cat -v):
argc=2
argv[1]=--flag
argv[2]=M-bM-^AM-#FIRSTMATE_OP: v1 launch-brief: # Current worker role contract
You are a crewmate: an autonomous worker agent managed by firstmate.
This section establishes your current identity before every project or task instruction below and supersedes any conflicting role identity in those instructions.
Do the assigned work yourself and report only to firstmate; do not adopt a firstmate or secondmate supervisor identity, delegate the task, run fleet supervision, or address t
Evidence: Live real Claude Code raw launch: doorbell, published record, worker pane

Source: Live real Claude Code raw launch: doorbell, published record, worker pane

\### fm-spawn output (raw launch: claude --model haiku --setting-sources project,local)
fm-gate-refuse: gate agent lifecycle permitted only against lab home /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp
WARNING: watcher still down (same stale episode; last beat: never, grace 300s) - full banner already printed this episode.
spawned rawlab-claude harness=claude kind=ship mode=local-only yolo=on window=primary:fm-rawlab-claude worktree=/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/treehouse/.treehouse/notes-24f9ca/3/notes
spawn-exit=0

\### staged launch line (tail)
/git-config.s1793346890.37388.14639.sh' || return 1 2>/dev/null || exit 1; env -u CURSOR_AGENT -u CURSOR_INVOKED_AS -u GEMINI_CLI claude --model haiku --setting-sources project,local ': Firstmate operational input waiting: read '\''/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/state/operational-inbox/1793346891-6ffed36530960c8f.msg'\'' and handle its contents as Firstmate operational input.'


\### published record (first 400 bytes, cat -v; M-bM-^AM-# = U+2063)
M-bM-^AM-#FIRSTMATE_OP: v1 launch-brief: # Current worker role contract
You are a crewmate: an autonomous worker agent managed by firstmate.
This section establishes your current identity before every project or task instruction below and supersedes any conflicting role identity in those instructions.
Do the assigned work yourself and report only to firstmate; do not adopt a firstmate or secondmate super

\### real Claude Code worker pane
 ▐▛███▛█   Claude Code v2.1.285
▝▜██████▀  Haiku 4.5 · Claude API
 ▝▝   ▝▝   /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/treehouse/.treehouse/notes-24f9ca/3/notes
❯ : Firstmate operational input waiting: read '/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/state/operational-inbox/1793346891-6ffed36530960c8f.msg' and handle its contents
  as Firstmate operational input.
  Read 1 file (ctrl+o to expand)
⏺ I'll handle this Firstmate operational input. Let me first verify isolation, then execute the task.
⏺ Bash(pwd -P && git rev-parse --show-toplevel)
  ⎿  Waiting…
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Bash command
   pwd -P && git rev-parse --show-toplevel
   Run shell command
 This command requires approval
 Do you want to proceed?
 ❯ 1. Yes
   2. Yes, and don’t ask again for: pwd -P
   3. No
 Esc to cancel · Tab to amend
Evidence: Live adversarial raw launches of real claude (env-wrapped, $(...) argument)

Source: Live adversarial raw launches of real claude (env-wrapped, $(...) argument)

\### wrap
fm-gate-refuse: gate agent lifecycle permitted only against lab home /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp
WARNING: watcher still down (same stale episode; last beat: never, grace 300s) - full banner already printed this episode.
warning: raw launch command is not a plain single command for a known harness (it is compound, substituted, wrapped, or starts with an unknown program), so the launch brief was not appended; put __BRIEF__ where the agent takes its prompt, for example: my-agent --flag "$(__OPINPUT__ encode launch-brief < __BRIEF__)"
spawned rawlab-wrap harness=env kind=ship mode=local-only yolo=on window=primary:fm-rawlab-wrap worktree=/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/treehouse/.treehouse/notes-24f9ca/4/notes
spawn-exit=0
staged tail: || return 1 2>/dev/null || exit 1; env -u CLAUDECODE claude --version
2.1.285 (Claude Code)
mini0@mini0:/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/treehouse/.treehouse/notes-24f9ca/4/notes/ >
\### subst
fm-gate-refuse: gate agent lifecycle permitted only against lab home /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp
WARNING: watcher still down (same stale episode; last beat: never, grace 300s) - full banner already printed this episode.
warning: raw launch command is not a plain single command for a known harness (it is compound, substituted, wrapped, or starts with an unknown program), so the launch brief was not appended; put __BRIEF__ where the agent takes its prompt, for example: my-agent --flag "$(__OPINPUT__ encode launch-brief < __BRIEF__)"
spawned rawlab-subst harness=claude kind=ship mode=local-only yolo=on window=primary:fm-rawlab-subst worktree=/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/treehouse/.treehouse/notes-24f9ca/5/notes
spawn-exit=0
staged tail: KED_AS -u GEMINI_CLI claude --model $(echo haiku | tr -d x) --version
2.1.285 (Claude Code)
mini0@mini0:/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.SaiMBp/treehouse/.treehouse/notes-24f9ca/5/notes/ >
Evidence: Targeted spawn dispatch-profile test log

Source: Targeted spawn dispatch-profile test log

ok - allowlist=absent preserves the operational floor and filters only when opted in
ok - allowlist=missing-config preserves the operational floor and filters only when opted in
ok - allowlist=enabled preserves the operational floor and filters only when opted in
ok - allowlist=empty preserves the operational floor and filters only when opted in
ok - invalid allowlist names refuse before launch or task publication
ok - inaccessible config with present allowlist refuses before launch or task publication
ok - inaccessible config with absent allowlist refuses before launch or task publication
ok - inaccessible ancestor with present allowlist refuses before launch or task publication
ok - inaccessible ancestor with absent allowlist refuses before launch or task publication
ok - secondmate launch inherits the allowlist for subsequent worker launches
ok - local inheritance preserves the allowlist on source errors and mirrors proven absence
ok - remote inheritance preserves the allowlist on source errors and mirrors proven absence
ok - Codex absent, bypass, and restricted modes preserve flags for ships, scouts, and secondmates
ok - invalid Codex permission files refuse spawns and relaunches before mutation
ok - Codex restricted mode leaves other harness launch flags unchanged
WORKER-RAN
WORKER-RAN
WORKER-RAN
WORKER-RAN
WORKER-RAN
WORKER-RAN
skip: fish not found (fish pane launch); only a static syntax check ran
ok - launch appends the hooks config while preserving inherited Git credentials in sh, bash and zsh
ok - fm-spawn: actual ship/scout launch commands deliver the worker role contract
ok - no --model/--effort records defaults and types the claude launch instructions
ok - a claude launch publishes the brief as an operational-inbox record and passes only the doorbell
ok - a secondmate claude launch publishes its brief record into the secondmate's own home
ok - a claude spawn whose launch-brief record cannot publish stops with a clear error and sends no launch
ok - non-cursor launches clear inherited Cursor identity markers
ok - relative home overrides ignore CDPATH and become absolute before spawn launch construction
ok - FM_HOME defaults resolve relative paths and preserve absolute spellings
ok - absolute override spellings are preserved in spawn launch paths
ok - unresolvable relative spawn overrides fail with named diagnostics
ok - active crew-dispatch profile requires an explicit harness for ship spawns
ok - active crew-dispatch profile requires an explicit harness for scout spawns
ok - active crew-dispatch profile allows an explicit resolved harness
ok - active crew-dispatch profile allows the legacy positional harness form
ok - active crew-dispatch profile allows the raw launch-command escape hatch
ok - a raw rovo launch is not given the positional launch-brief argument
ok - a plain single raw launch of a known harness gets the launch brief appended
ok - a raw claude launch gets the record-backed launch-brief doorbell
ok - a raw launch that is not a plain single known-harness command launches unchanged with a warning
ok - a compound raw launch places the brief where it names __BRIEF__, with nothing appended
ok - a chained raw launch commits through the AI-trailer strip in every step
ok - claude receives --model and --effort profile flags
ok - codex receives --model and model_reasoning_effort profile flags
ok - codex Luna receives --model and model_reasoning_effort max profile flags
ok - codex omits max for models without the catalog capability
ok - a codex crewmate requests no hook layer and keeps its turn-end signal
ok - a codex secondmate keeps its project hooks and uses automatic approval review
ok - Codex defaults remain bypass; persistent fallback is direct and invalid values refuse
ok - grok receives --model and --reasoning-effort profile flags
ok - grok omits unsupported max reasoning effort
ok - grok omits unsupported xhigh reasoning effort
ok - cursor receives its model-qualified reasoning class and exact task workspace
ok - cursor refuses model ids absent from its resolved binary's live catalog
ok - cursor preserves the requested model when its live catalog is unreachable
ok - opencode receives the model and effort through its config
ok - opencode without an effort keeps the model pin and omits variant
ok - opencode emits the variant for an effort the openai family exposes
ok - opencode omits the variant for an effort outside the model family's list
ok - opencode 1.x keeps the --model flag and the variant-only config
ok - opencode 1.x without an effort keeps its permission-only launch config
ok - opencode probe and launch share the same resolved executable path
ok - opencode with an unparseable version falls back to the 1.x launch path
ok - opencode 2 delivery gate accepts an auto-submitted brief without pressing Enter
ok - opencode 2 delivery gate sends exactly one Enter for a brief left in the composer
ok - opencode 2 delivery gate keeps polling an unknown composer and sends one Enter once it reads pending
ok - opencode 2 delivery gate sends its one Enter at the deadline when the composer only reads unknown
ok - opencode 2 delivery gate fails the spawn when the brief turn never starts
ok - opencode 1.x launches keep their pre-gate delivery
ok - opencode provisions a busy-state plugin for every detected major, including the 1.x fallback
ok - native effort validator checks harness and model as separate axes
ok - Ultra is explicit for native Pi and Pi-signed, including direct-PR, and refuses unsupported profiles before provisioning
ok - batch dispatch preserves native Ultra in metadata and launch flags
ok - Pi scout spawn enters the recorded worktree before launch
ok - pi receives --model and --thinking max profile flags
ok - Pi launch probing omits --tui-mode on older Pi and preserves it on supporting Pi
ok - pi-signed shares Pi launch semantics while preserving its configured and recorded identity
ok - pi-signed refuses safely and actionably when the selected executable is unavailable
ok - pi-signed is a distinct persistent secondmate runtime with shared Pi supervision semantics
ok - seeded Pi/pi-signed secondmate launches carry session --approve when advertised
ok - ordinary Pi worker launches omit --approve
ok - Pi approve probing omits --approve when help does not advertise it
ok - Pi and pi-signed ship and scout launches carry the configured tool exclusions
ok - Pi worker registries report unmatched and unverified exclusions without reporting exact matches
ok - an absent or empty exclusion list leaves every runtime unaffected
ok - a runtime that cannot hide tools, or a raw launch, refuses a non-empty exclusion list
ok - a malformed exclusion entry refuses the spawn before any launch or record
ok - Pi and pi-signed ship and scout launches refuse when the exclusion file disappears after its checks
ok - exclusions stay in the home that configured them and skip secondmate agents
ok - batch dispatch forwards shared --harness, --model, and --effort to every pair
ok - claude forwards firstmate's CLAUDE_CONFIG_DIR so the crewmate uses the same credential store
ok - the primary-owned Lavish server address reaches every worker launch
cat: /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-spawn-dispatch-profile.AwoRAR/profile-lavish-ambient/pane.log: No such file or directory
ok - absent Lavish configuration preserves the destination environment
ok - claude omits the config-dir prefix when firstmate runs with the single-store default
ok - absent config/claude-permission-mode defaults to --dangerously-skip-permissions
ok - config/claude-permission-mode=auto opts into the model-reviewed launch
ok - explicit config/claude-permission-mode=bypass uses --dangerously-skip-permissions
ok - config/claude-permission-mode=auto reaches scout launches too
ok - claude worker launches cover the task-channel directories in bypass and auto modes
ok - an unrecognized config/claude-permission-mode token refuses before any endpoint or metadata
ok - config/claude-permission-mode changes claude launches only
ok - non-claude harnesses do not receive the claude CLAUDE_CONFIG_DIR prefix
ok - a claude task launch establishes only Firstmate's task control channels through the system prompt
ok - a persistent claude secondmate keeps its supervisor contract without a task-worker authority overlay
ok - fm-spawn: a Claude launch longer than 1024 bytes is delivered intact through the staging path
ok - a claude crewmate launch carries the attribution-off policy in its own settings
ok - keep-ai-trailers omits Claude attribution settings and the pane strip hooks
ok - keep-ai-trailers is inherited so a secondmate's crew launch keeps AI trailers
ok - a claude secondmate launch carries the attribution-off policy too
ok - active crew-dispatch profile does not block secondmate launches
# all fm-spawn-dispatch-profile tests passed
Evidence: What the generic agent received for 'custom-agent --flag; cleanup'
warning: raw launch command is not a plain single command for a known harness (...), so the launch brief was not appended; put __BRIEF__ where the agent takes its prompt
staged: ...; custom-agent --flag; cleanup
custom-agent: argc=1 argv[1]=--flag
cleanup: argc=0

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • ⚠️ bin/fm-spawn.sh:2926 - The known-harness allowlist in raw_launch_takes_brief includes harnesses whose named templates do not take a positional prompt. The recorded decision says to append only for harnesses that 'take a positional prompt'. Three listed harnesses don't: opencode (template line 2678 uses --prompt, and OpenCode's positional argument is a project path), polytoken (line 2862 uses new ... --prompt), and agy (line 2729 uses --prompt-interactive). Example: the raw command opencode --model anthropic/x passes every check, so the typed brief envelope is appended as a bare positional argument. OpenCode then reads the envelope as a project directory instead of a prompt, so the worker starts wrong or fails, with no warning. The same happens to polytoken --config-dir d new and agy --model m. Fix: remove opencode, polytoken and agy from the case list at bin/fm-spawn.sh:2926. Those raw commands then take the warning path and launch unchanged. Also update the header text at bin/fm-spawn.sh:246-247, which promises the brief to 'a known harness that takes a positional prompt'.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 5 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Compound raw launch 'custom-agent --flag; cleanup' launches unchanged with a BRIEF warning; cleanup gets no brief argument ✅ pass live raw-generic-live.txt (Scenario A): spawn printed the warning, the staged launch ends '; custom-agent --flag; cleanup', custom-agent argv was only --flag, and cleanup argc=0
Trailing-semicolon raw launch 'custom-agent --flag;' launches unchanged with the warning; no encoded brief reaches the shell ✅ pass live raw-generic-live.txt (Scenario B): warning printed, staged launch ends 'custom-agent --flag;', agent argv was only --flag
BRIEF placed inside a compound command delivers the typed launch-brief envelope at that position with no warning ✅ pass live raw-generic-live.txt (Scenario C): no warning; argv[2] begins with U+2063 'FIRSTMATE_OP: v1 launch-brief: # Current worker role contract'; staged line keeps '; cleanup' after the placeholder
Real raw launch with a real harness CLI: 'claude --model haiku --setting-sources project,local' gets the record-backed doorbell and the worker reads the brief ✅ pass live raw-claude-live.txt: staged launch ends with the ': Firstmate operational input waiting: read <operational-inbox>/...msg' doorbell; the record holds the U+2063 launch-brief envelope; the Claude Code 2…
Adversarial: a wrapped ('env -u CLAUDECODE claude --version') or $(...)-argument raw launch of a known harness is not given the brief and warns ✅ pass live raw-adversarial-live.txt: both printed the warning, the staged launches end exactly as given, both ran ('2.1.285 (Claude Code)'), and the only operational record in the lab came from the plain claude…
Raw kimi/rovo launches get no positional brief (post-readiness pointer path) ⏸️ untested no The kimi and rovo CLIs are not on PATH on this machine (the author confirmed they are not installed), and installing them is out of scope for this gate. Only the generic-harness and claude paths could…
  • bash tests/fm-spawn-dispatch-profile.test.sh (the targeted spawn test file containing the new raw-launch tests; all passed, including raw rovo no-brief, plain known harness gets brief, raw claude doorbell, other raw launches unchanged with warning, compound BRIEF placement, and the unmodified chained/inherited-git-config originals)
  • Live lab: bin/fm-spawn.sh rawlab-cmpd &lt;lab&gt;/projects/notes --mode local-only --yolo on &#39;custom-agent --flag; cleanup&#39; with an argv-dumping custom-agent and cleanup on PATH
  • Live lab: bin/fm-spawn.sh rawlab-trail ... &#39;custom-agent --flag;&#39;
  • Live lab: bin/fm-spawn.sh rawlab-place ... &#39;custom-agent --flag &#34;$(__OPINPUT__ encode launch-brief &lt; __BRIEF__)&#34;; cleanup&#39;
  • Live lab with real Claude Code CLI: bin/fm-spawn.sh rawlab-claude ... &#39;claude --model haiku --setting-sources project,local&#39;, then captured the worker pane on the fm-lab socket
  • Live lab adversarial with real claude binary: &#39;env -u CLAUDECODE claude --version&#39; and &#39;claude --model $(echo haiku | tr -d x) --version&#39;
  • Teardown: killed the fm-lab tmux server, removed the lab's ~/.claude.json trust entries with an atomic replace (3 removed, 0 remaining), removed the stray ~/.treehouse/notes-24f9ca pool created by the first lab spawn, the /tmp/fm-rawlab-* task temp dirs, and the lab home
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@trillium

Copy link
Copy Markdown
Contributor Author

CI note: the only failing check, Behavior portable serial 1, fails in the unrelated timing-sensitive tests/fm-supervision-host.test.sh (host wake hand-off wait_until timeout); this PR does not touch that area.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: thank you @trillium, this is a clear, small change and the rovo catch from review is a good one.

What I checked at head f6c486d1 against main 7da4c362: for a raw --harness '<cmd>' launch, bin/fm-spawn.sh now appends "$(… encode launch-brief < __BRIEF__)" after the caller's argv. kimi*/rovo* and commands that already name __BRIEF__ are skipped. Named adapters are untouched.

Contract-class: new-default (my own read). Main specifies the raw escape hatch as passing the captain's command through as written. The existing test says "nothing else may rewrite the captain's own command" and asserts that the raw command is unchanged. This PR changes that default for every raw launch, which is why the tests needed && true / ; : to absorb the extra argument. Starting with the brief is clearly useful. Still, changing what happens to a command the captain typed is the captain's call, so this won't auto-merge. Once it's otherwise ready, I'll put it to the captain as a merge-or-leave decision.

VISION:

  • One captain aligns: the worker no longer starts at an empty prompt.
  • Authority is a captain call: fm-spawn now edits the captain's own raw command with no opt-in.
  • Scripts aligns: a deterministic append.
  • Restart aligns.
  • Spine aligns.
  • Vendor: cannot tell. The skip list knows two harnesses that reject a positional prompt, but other unverified CLIs may reject one too. Raw launch exists precisely for unknown adapters.
  • Scope aligns.

CI: the only red is Behavior portable serial 1 in tests/fm-supervision-host.test.sh ("return-fail: the host did not hand the wake to main"). That's a known timing flake we're working on separately. It isn't your change, and I've re-run that job.

One ask, whenever you have time: the "live" rows in the description are the spawn tests with a fake pane. Could you do one real raw launch with an actual harness CLI (a generic one, plus kimi or rovo if you can) and post what the agent received? You could also re-raise through git push no-mistakes with that evidence. That's the only thing waiting on you. The rest is CI and then the captain.

@trillium

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I don't have kimi or rovo installed, so I can't test the raw launch against either of those.

@trillium

Copy link
Copy Markdown
Contributor Author

Closing: opened without intent. Sorry for the noise.

@trillium trillium closed this Oct 29, 2026
@trillium trillium reopened this Oct 29, 2026
@trillium

Copy link
Copy Markdown
Contributor Author

Reopened: the earlier close was a mistake on my side. This PR stands as originally written.

@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no outstanding findings remain.

Reviews (4) · Last reviewed commit: "no-mistakes(ci): ci-3 (Greptile: shell c..." · Reviewed by Greptile

Comment thread bin/fm-spawn.sh Resolved
@trillium
trillium force-pushed the fix/spawn-raw-launch-brief branch from f6c486d to 122ec52 Compare October 30, 2026 06:30
@trillium trillium changed the title fix(spawn): append launch brief to raw launch commands fix(bin): append the launch brief to raw fm-spawn launch commands Oct 30, 2026
Comment thread bin/fm-spawn.sh Resolved
Comment thread bin/fm-spawn.sh Resolved
trillium and others added 9 commits October 30, 2026 00:17
A raw launch command skipped the launch-brief arg that every named adapter template embeds, so the agent started at an empty prompt. Append it after the caller's own argv. Kimi rejects a positional prompt and keeps its post-readiness brief pointer, and a command that already names __BRIEF__ is left alone.
@trillium
trillium force-pushed the fix/spawn-raw-launch-brief branch from 122ec52 to f9cab7c Compare October 30, 2026 07:57
@trillium trillium changed the title fix(bin): append the launch brief to raw fm-spawn launch commands fix(bin): append the launch brief to raw launch commands Oct 30, 2026
Comment thread bin/fm-spawn.sh Resolved
…he rule it breaks: the launch brief is only appended automatically when the raw command is a plain single command, so the appended argument actually reaches the agent's argv. A trailing `#` comment breaks that rule because the shell treats the appended brief as part of the comment. That means `codex --model x # use default permissions` would start the worker with no task and no warning. All the places this rule must hold: - the metacharacter check in `raw_launch_takes_brief` (`bin/fm-spawn.sh`), which is the one place every append decision goes through, both the claude doorbell path and the typed-envelope path; - the function's comment block; - the usage text in the fm-spawn header. Changes: - `bin/fm-spawn.sh`: added `*'#'*` to the metacharacter case in `raw_launch_takes_brief`. A raw command containing `#` now fails the check and goes down the existing path: it launches exactly as given, with the existing one-line warning suggesting `__BRIEF__`. Added `#` to the character lists in the function comment and the header usage text. - `tests/fm-spawn-dispatch-profile.test.sh`: added `"codex --model x # use default permissions"` to the cases in `test_other_raw_launches_launch_unchanged_with_a_warning`. That test runs the real spawn and checks three things: it exits 0, the warning is printed, and the emitted launch is the unchanged command with no launch-brief appended. Added "commented" to the test's comment. Following the recorded decisions, nothing was changed for ci-1 (the accepted label-gated Full suite) or ci-2 (a stale comment). Verification is not finished. The test-suite run, a confirmation run with the fix reverted, and the shellcheck run were still going in the background when I had to return this result, so I have no pass/fail output for them yet. By inspection, a command with `#` cannot reach the append branch now, and plain commands such as `codex --flag` and `claude --model x` still get the brief because they contain no `#`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants